﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace OpenCvSharp
{
    public class CvBlob
    {
        public static Mat Threshold(Mat image, double lowThreshold = 0, double highThreshold = 10, bool darkBackground = true)
        {
            using (Mat threshold1 = new Mat())
            using (Mat threshold2 = new Mat())
            {
                if (darkBackground)
                {
                    Cv2.Threshold(image, threshold1, lowThreshold, 255, ThresholdTypes.BinaryInv | ThresholdTypes.Otsu);
                    Cv2.Threshold(image, threshold2, highThreshold, 255, ThresholdTypes.BinaryInv | ThresholdTypes.Otsu);
                }
                else
                {
                    Cv2.Threshold(image, threshold1, lowThreshold, 255, ThresholdTypes.Binary | ThresholdTypes.Otsu);
                    Cv2.Threshold(image, threshold2, highThreshold, 255, ThresholdTypes.Binary | ThresholdTypes.Otsu);
                }
                Mat ret = new Mat();
                Cv2.BitwiseAnd(threshold1, threshold2, ret);

#if DEBUG
                using (Mat dst = new Mat(image.Size(), MatType.CV_8UC3, Scalar.Black))
                using (Mat fillColor = new Mat(image.Size(), MatType.CV_8UC3, Scalar.Red))
                {
                    fillColor.CopyTo(dst, ret);
                    dst.ImShow("Threshold");
                }
#endif

                return ret;
            }
        }

        public static Mat[] Connection(Mat region)
        {
            ConnectedComponents conCom = Cv2.ConnectedComponentsEx(region, PixelConnectivity.Connectivity8, ConnectedComponentsAlgorithmsTypes.Default);
            int rows = conCom.Labels.GetLength(0);
            int cols = conCom.Labels.GetLength(1);
            int num = conCom.LabelCount - 1;

            Mat[] regions = new Mat[num];

            using (Mat labels = new Mat(rows, cols, MatType.CV_32SC1, conCom.Labels.GetBuffer(), 0L))
            {
                for (int i = 0; i < num; i++)
                {
                    regions[i] = new Mat(rows, cols, MatType.CV_32SC1, Scalar.All(i + 1));
                    Cv2.Compare(labels, regions[i], regions[i], CmpType.EQ);
                    Cv2.Normalize(regions[i], regions[i], 0, 255, NormTypes.MinMax, MatType.CV_8U);
                }
            }

#if DEBUG
            using (Mat dst = new Mat(rows, cols, MatType.CV_8UC3, Scalar.Black))
            {
                Scalar color;
                ConnectedComponents.Blob blob;
                for (int i = 0; i < num; i++)
                {
                    color = Scalar.RandomColor();
                    blob = conCom.Blobs[i + 1];
                    using (Mat fill = new Mat(rows, cols, MatType.CV_8UC3, color))
                    {
                        Cv2.CopyTo(fill, dst, regions[i]);
                        Cv2.Rectangle(dst, blob.Rect, Scalar.White, 1);
                        Cv2.DrawMarker(dst, blob.Centroid.ToPoint(), Scalar.White, MarkerTypes.Cross);
                        Cv2.PutText(dst, i.ToString(), new Point(blob.Left, blob.Top - 5), HersheyFonts.HersheySimplex, 0.5, Scalar.White, 1);
                    }
                }
                dst.ImShow("Connection count:" + num);
            }
#endif
            return regions;
        }

        public static Mat[] SelectShape(Mat[] regions,CvShapeParam[] paramNames, bool operationIsAnd, double[] minValues, double[] maxValues)
        {
            Mat[] matchs = new Mat[regions.Length];
            int matchIndex = 0;
            Point[][] contour;
            double val,min, max;
            Point2f tmp2f;
            float tmp1f;
            RotatedRect tmp5d;
            for (int i = 0; i < regions.Length; i++)
            {
                contour = Cv2.FindContoursAsArray(regions[i], RetrievalModes.External, ContourApproximationModes.ApproxNone);
                if(contour.Length!=1)
                {
                    throw new ArgumentException("regions index " + i);
                }
                for (int j = 0; j < paramNames.Length; j++)
                {
                    min = minValues[j];
                    max = maxValues[j];
                    switch (paramNames[j])
                    {
                        case CvShapeParam.Area:
                            val = Cv2.ContourArea(contour[0], false);
                            break;
                        case CvShapeParam.ArcLength:
                            val = Cv2.ArcLength(contour[0], false);
                            break;
                        case CvShapeParam.OuterRectX:
                            val = Cv2.BoundingRect(contour[0]).X;
                            break;
                        case CvShapeParam.OuterRectY:
                            val = Cv2.BoundingRect(contour[0]).Y;
                            break;
                        case CvShapeParam.OuterRectWidth:
                            val = Cv2.BoundingRect(contour[0]).Width;
                            break;
                        case CvShapeParam.OuterRectHeight:
                            val = Cv2.BoundingRect(contour[0]).Height;
                            break;
                        case CvShapeParam.MinOuterRectX:
                            val = Cv2.MinAreaRect(contour[0]).Center.X;
                            break;
                        case CvShapeParam.MinOuterRectY:
                            val = Cv2.MinAreaRect(contour[0]).Center.Y;
                            break;
                        case CvShapeParam.MinOuterRectAngle:
                            val = Cv2.MinAreaRect(contour[0]).Angle;
                            break;
                        case CvShapeParam.MinOuterRectWidth:
                            val = Cv2.MinAreaRect(contour[0]).Size.Width;
                            break;
                        case CvShapeParam.MinOuterRectHeight:
                            val = Cv2.MinAreaRect(contour[0]).Size.Height;
                            break;
                        case CvShapeParam.MinOuterCircleX:
                            Cv2.MinEnclosingCircle(contour[0], out tmp2f, out _);
                            val = tmp2f.X;
                            break;
                        case CvShapeParam.MinOuterCircleY:
                            Cv2.MinEnclosingCircle(contour[0], out tmp2f, out _);
                            val = tmp2f.Y;
                            break;
                        case CvShapeParam.MinOuterCircleRadius:
                            Cv2.MinEnclosingCircle(contour[0], out _, out tmp1f);
                            val = tmp1f;
                            break;
                        case CvShapeParam.Circularity:
                            //Cv2.MinEnclosingCircle(contour[0], out _, out tmp1f);
                            //val = Cv2.ContourArea(contour[0], false) / (Cv2.PI * Math.Pow(tmp1f, 2d));
                            //opencv算法(features2d/blobdetector.cpp 226行)
                            val = 4d * Cv2.PI * Cv2.ContourArea(contour[0], false) / Math.Pow(Cv2.ArcLength(contour[0], false), 2d);                        
                            break;
                        case CvShapeParam.Convexity:
                            val = Cv2.ContourArea(contour[0], false) / Cv2.ContourArea(Cv2.ConvexHull(contour[0], false));
                            break;
                        case CvShapeParam.Rectangularity:
                            tmp5d = Cv2.MinAreaRect(contour[0]);
                            val = Cv2.ContourArea(contour[0], false) / (tmp5d.Size.Width * tmp5d.Size.Height);
                            break;
                        case CvShapeParam.MinOuterEllipseX:
                            val = Cv2.FitEllipse(contour[0]).Center.X;
                            break;
                        case CvShapeParam.MinOuterEllipseY:
                            val = Cv2.FitEllipse(contour[0]).Center.Y;
                            break;
                        case CvShapeParam.MinOuterEllipseAngle:
                            val = Cv2.FitEllipse(contour[0]).Angle;
                            break;
                        case CvShapeParam.MinOuterEllipseLongAxis:
                            tmp5d = Cv2.FitEllipse(contour[0]);
                            val = Math.Max(tmp5d.Size.Width, tmp5d.Size.Height);
                            break;
                        case CvShapeParam.MinOuterEllipseShortAxis:
                            tmp5d = Cv2.FitEllipse(contour[0]);
                            val = Math.Min(tmp5d.Size.Width, tmp5d.Size.Height);
                            break;
                        default:
                            val = min-1d;
                            break;
                    }

                    if(val >= min && val <= max)
                    {
                        if(!operationIsAnd)
                        {
                            matchs[matchIndex] = regions[i].Clone();
                            matchIndex++;
                            break;
                        }
                        if(j == paramNames.Length -1)
                        {
                            matchs[matchIndex] = regions[i].Clone();
                            matchIndex++;
                            break;
                        }
                    }
                    else
                    {
                        if(operationIsAnd)
                        {
                            break;
                        }
                    }
                }
            }
            Mat[] result = new Mat[matchIndex];
            Array.Copy(matchs, 0, result, 0, matchIndex);
            return result;
        }

        public static double RegionFeatures(Mat region,CvShapeParam paramName)
        {
            Point[][] contour = Cv2.FindContoursAsArray(region, RetrievalModes.External, ContourApproximationModes.ApproxNone);
            if (contour.Length != 1)
            {
                throw new ArgumentException("region");
            }
            double val;
            Point2f tmp2f;
            float tmp1f;
            RotatedRect tmp5d;
            switch (paramName)
            {
                case CvShapeParam.Area:
                    val = Cv2.ContourArea(contour[0], false);
                    break;
                case CvShapeParam.ArcLength:
                    val = Cv2.ArcLength(contour[0], false);
                    break;
                case CvShapeParam.OuterRectX:
                    val = Cv2.BoundingRect(contour[0]).X;
                    break;
                case CvShapeParam.OuterRectY:
                    val = Cv2.BoundingRect(contour[0]).Y;
                    break;
                case CvShapeParam.OuterRectWidth:
                    val = Cv2.BoundingRect(contour[0]).Width;
                    break;
                case CvShapeParam.OuterRectHeight:
                    val = Cv2.BoundingRect(contour[0]).Height;
                    break;
                case CvShapeParam.MinOuterRectX:
                    val = Cv2.MinAreaRect(contour[0]).Center.X;
                    break;
                case CvShapeParam.MinOuterRectY:
                    val = Cv2.MinAreaRect(contour[0]).Center.Y;
                    break;
                case CvShapeParam.MinOuterRectAngle:
                    val = Cv2.MinAreaRect(contour[0]).Angle;
                    break;
                case CvShapeParam.MinOuterRectWidth:
                    val = Cv2.MinAreaRect(contour[0]).Size.Width;
                    break;
                case CvShapeParam.MinOuterRectHeight:
                    val = Cv2.MinAreaRect(contour[0]).Size.Height;
                    break;
                case CvShapeParam.MinOuterCircleX:
                    Cv2.MinEnclosingCircle(contour[0], out tmp2f, out _);
                    val = tmp2f.X;
                    break;
                case CvShapeParam.MinOuterCircleY:
                    Cv2.MinEnclosingCircle(contour[0], out tmp2f, out _);
                    val = tmp2f.Y;
                    break;
                case CvShapeParam.MinOuterCircleRadius:
                    Cv2.MinEnclosingCircle(contour[0], out _, out tmp1f);
                    val = tmp1f;
                    break;
                case CvShapeParam.Circularity:
                    //Cv2.MinEnclosingCircle(contour[0], out _, out tmp1f);
                    //val = Cv2.ContourArea(contour[0], false) / (Cv2.PI * Math.Pow(tmp1f, 2d));
                    //opencv算法(features2d/blobdetector.cpp 226行)
                    val = 4d * Cv2.PI * Cv2.ContourArea(contour[0], false) / Math.Pow(Cv2.ArcLength(contour[0], false), 2d);
                    break;
                case CvShapeParam.Convexity:
                    val = Cv2.ContourArea(contour[0], false) / Cv2.ContourArea(Cv2.ConvexHull(contour[0], false));
                    break;
                case CvShapeParam.Rectangularity:
                    tmp5d = Cv2.MinAreaRect(contour[0]);
                    val = Cv2.ContourArea(contour[0], false) / (tmp5d.Size.Width * tmp5d.Size.Height);
                    break;
                case CvShapeParam.MinOuterEllipseX:
                    val = Cv2.FitEllipse(contour[0]).Center.X;
                    break;
                case CvShapeParam.MinOuterEllipseY:
                    val = Cv2.FitEllipse(contour[0]).Center.Y;
                    break;
                case CvShapeParam.MinOuterEllipseAngle:
                    val = Cv2.FitEllipse(contour[0]).Angle;
                    break;
                case CvShapeParam.MinOuterEllipseLongAxis:
                    tmp5d = Cv2.FitEllipse(contour[0]);
                    val = Math.Max(tmp5d.Size.Width, tmp5d.Size.Height);
                    break;
                case CvShapeParam.MinOuterEllipseShortAxis:
                    tmp5d = Cv2.FitEllipse(contour[0]);
                    val = Math.Min(tmp5d.Size.Width, tmp5d.Size.Height);
                    break;
                default:
                    val = double.NaN;
                    break;
            }
            return val;
        }

    }

    public enum CvShapeParam
    {
        Area = 0,
        ArcLength= 1,
        OuterRectX = 2,
        OuterRectY = 3,
        OuterRectWidth = 4,
        OuterRectHeight = 5,
        MinOuterRectX = 6,
        MinOuterRectY = 7,
        MinOuterRectAngle = 8,
        MinOuterRectWidth = 9,
        MinOuterRectHeight = 10,
        MinOuterCircleX = 11,
        MinOuterCircleY = 12,
        MinOuterCircleRadius = 13,
        Circularity = 14,
        Convexity=15,
        Rectangularity=16,
        MinOuterEllipseX=17,
        MinOuterEllipseY=18,
        MinOuterEllipseAngle = 19,
        MinOuterEllipseLongAxis = 20,
        MinOuterEllipseShortAxis=21
    }
}